﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic.Shell.Hosting
{
    using System;
    using System.IO;
    using System.IO.Packaging;
    using System.Text;
    using System.Xml.Linq;
    using ConnectBasic.Shell.Hosting.OpenXmlFileProjectStoreInternals;

    /// <summary>
    /// Provides an implementation of the <see cref="IProjectStore" />
    /// interface which uses an OOXML file as a backing store.
    /// </summary>
    public class OpenXmlFileProjectStore : IProjectStore
    {

        /// <summary>
        /// Gets the namespace used for all XML elements.
        /// </summary>
        internal static readonly XNamespace NS = XNamespace.Get("http://connectbasic.net/schema/ooxml-project/1");

        private static Uri URI_DIRECTORY = new Uri("/macro/directory.xml", UriKind.Relative);
        private const string TYPE_DIRECTORY = "application/xml";
        private const string TYPE_ITEM = "application/xml";

        private string mFilePath;
        private Package mBasePackage;
        private PackagePart mDirectoryPart;

        private ProjectStoreFolderCollection mFolders;
        private ProjectStoreItemCollection mItems;
        private ProjectStoreProperties mProperties;

        #region .ctor

        public OpenXmlFileProjectStore(string filePath, FileMode fileMode)
        {
            mFilePath = filePath;

            // Validate the fileMode
            if (fileMode == FileMode.Append || fileMode == FileMode.Truncate || fileMode == FileMode.Create)
            {
                throw new ArgumentOutOfRangeException("fileMode");
            }

            // Open the file
            var isNew = !File.Exists(filePath);
            mBasePackage = Package.Open(filePath, fileMode);

            // Locate the the project
            if (mBasePackage.PartExists(URI_DIRECTORY))
            {
                this.LoadExisting();
            }
            else if (isNew)
            {
                this.CreateNew();
            }
            else
            {
                throw new FileLoadException(filePath);
            }
        }

        private void LoadExisting()
        {
            mDirectoryPart = mBasePackage.GetPart(URI_DIRECTORY);
        }

        private void CreateNew()
        {

            // Create the template directory 
            var properties = new XElement(NS + "Properties");
            var rootFolders = new XElement(NS + "Folders");
            var rootItems = new XElement(NS + "Items");

            var templateDirectory =
                new XElement(NS + "Directory",
                    properties,
                    rootFolders,
                    rootItems
                );

            // Create the part
            mDirectoryPart = mBasePackage.CreatePart(URI_DIRECTORY, TYPE_DIRECTORY, CompressionOption.Fast);

            using (var stream = mDirectoryPart.GetStream(FileMode.Create))
            {
                using (var writer = new StreamWriter(stream, Encoding.UTF8))
                {
                    templateDirectory.Save(writer);
                }
            }

            // Create the collections
            mProperties = new ProjectStoreProperties(properties);
            mFolders = new ProjectStoreFolderCollection(this, rootFolders);
            mItems = new ProjectStoreItemCollection(this, rootItems);

            // Set up default properties
            mProperties.SetPropertyText("DisplayName", Path.GetFileNameWithoutExtension(mFilePath));
            mProperties.SetPropertyText("OptionStrict", "Off");
            mProperties.SetPropertyText("OptionBase", "1");
        }

        #endregion

        #region IProjectStore Members

        /// <summary>
        /// Saves the project to its backing store.
        /// </summary>
        public void Save()
        {
            mBasePackage.Flush();
        }

        public IProjectStoreProperties Properties
        {
            get { return mProperties; }
        }

        public IProjectStoreFolderCollection Folders
        {
            get { return mFolders; }
        }

        public IProjectStoreItemCollection Items
        {
            get { return mItems; }
        }

        #endregion

        #region Internal Methods

        internal PackagePart AllocatePart(string suggestedName)
        {

            // See if a part with the suggested name already exists
            string suggestedNameBase = suggestedName;
            Uri partUri = new Uri("/macro/" + suggestedName, UriKind.Relative);
            int suffix = 2;

            while (mBasePackage.PartExists(partUri))
            {
                partUri = new Uri(string.Format("/macro/{0}{1}", suggestedName, suffix++),
                                  UriKind.Relative);
            }

            // Create the part
            return mBasePackage.CreatePart(partUri, TYPE_ITEM, CompressionOption.Fast);
        }

        #endregion

    }
}
